#include "DrawingArea.hpp"
#include <QPainter>
#include <QString>
#include <QPixmap>
#include <QMouseEvent>

#include <iostream>
#include <sstream>

#define NODE_NAME_SIZE 20
#define FONT_SIZE 12

DrawingArea::DrawingArea(QWidget *parent) :
    QWidget(parent)
{
    setMouseTracking(true);
    _zoom = 1.0;

    _nodeIcons[Node::ABSTRACT].load(":new/icons/nuage.png");
    _nodeIcons[Node::USER].load(":new/icons/user.png");
    _nodeIcons[Node::APPLICATION].load(":new/icons/application.png");
    _nodeIcons[Node::INTERACTION].load(":new/icons/interaction.png");

    _relationSymbols[Node::NONE] = "";
    _relationSymbols[Node::CHOICE] = "[ ]";
    _relationSymbols[Node::ORDER_INDEPENDANCE] = "|=|";
    _relationSymbols[Node::INTERLEAVING] = "|||";
    _relationSymbols[Node::SYNCHRONIZATION] = "|[]|";
    _relationSymbols[Node::DISABLING] = "[>";
    _relationSymbols[Node::SUSPEND_RESUME] = "|>";
    _relationSymbols[Node::SEQUENCE] = QString(">>");
    _relationSymbols[Node::SEQUENTIAL_ENABLING_INFO] = "[]>>";

    _state = IDLE;

    _newNodePos = NULL;
    _closestNode = NULL;
}

void DrawingArea::setModel(Model* model)
{
    _model = model;
    repaint();
}

DrawingArea::~DrawingArea()
{
}

void DrawingArea::repaint()
{
    _model->getWorkingTree()->getRoot()->getExtremes(&minX, &minY, &maxX, &maxY);
    minX -= 60;
    minY -= 60;
    maxX += 60;
    maxY += 60;
    QRect geo(geometry().x(), geometry().y(),
              (maxX-minX+ NODE_SIZE)*_zoom,
              (maxY-minY+ NODE_SIZE + NODE_NAME_SIZE)*_zoom);
    if(geo != geometry())
        setGeometry(geo);
    QWidget::repaint();
}

void DrawingArea::paintEvent(QPaintEvent* e)
{
    QWidget::paintEvent(e);
    QPainter painter(this);
    painter.setFont(QFont(0, FONT_SIZE*_zoom));
    _closestNodePos = QPoint(NODE_SIZE / 2, NODE_SIZE + NODE_NAME_SIZE);
    _closestNode = _model->getWorkingTree()->getRoot();
    drawSubTree(painter, _model->getWorkingTree()->getRoot(), -minX, -minY);
    painter.setPen(Qt::green);
    if(_newNodePos)
    {
        int dx = _newNodePos->x() - _closestNodePos.x();
        int dy = _newNodePos->y() - _closestNodePos.y();

        if((dy > qAbs(dx)) || !_closestNode->getFather())
        {
            painter.drawLine((_newNodePos->x() - minX) * _zoom,
                             (_newNodePos->y() - minY) * _zoom,
                             (_closestNodePos.x() - minX) * _zoom,
                             (_closestNodePos.y() - minY) * _zoom);
            painter.drawPixmap((_newNodePos->x() - minX - NODE_SIZE / 4) * _zoom,
                               (_newNodePos->y() - minY - NODE_SIZE / 4) * _zoom,
                               NODE_SIZE / 2 * _zoom, NODE_SIZE / 2 * _zoom,
                               _nodeIcons[_closestNode->getType()]);
        }
        else
        {
            int x1 = _closestNodePos.x() - _closestNode->getX();
            int y1 = _closestNodePos.y() - _closestNode->getY();
            painter.drawLine((_newNodePos->x() - minX) * _zoom,
                             (_newNodePos->y() - minY) * _zoom,
                             (x1 - minX) * _zoom,
                             (y1 - minY) * _zoom);
            painter.drawLine((_newNodePos->x() - minX) * _zoom,
                             (_newNodePos->y() - minY) * _zoom,
                             (_closestNodePos.x() - minX) * _zoom,
                             (_closestNodePos.y() - minY - NODE_SIZE / 2 - NODE_NAME_SIZE) * _zoom);
            if((dx >= 0) && _closestNode->getRightSibling())
                painter.drawLine((_newNodePos->x() - minX) * _zoom,
                                 (_newNodePos->y() - minY) * _zoom,
                                 (x1 + _closestNode->getRightSibling()->getX() - minX) * _zoom,
                                 (y1 + _closestNode->getRightSibling()->getY() - minY - NODE_SIZE / 2 - NODE_NAME_SIZE) * _zoom);
            else if(dx < 0)
            {
                unsigned int i = 0;
                for(i = 0; i < _closestNode->getFather()->getChildren().size(); i++)
                    if(_closestNode->getFather()->getChildren()[i] == _closestNode)
                        break;
                if(i > 0)
                {
                    Node* leftSibling = _closestNode->getFather()->getChildren()[i - 1];
                    painter.drawLine((_newNodePos->x() - minX) * _zoom,
                                     (_newNodePos->y() - minY) * _zoom,
                                     (x1 + leftSibling->getX() - minX) * _zoom,
                                     (y1 + leftSibling->getY() - minY - NODE_SIZE / 2 - NODE_NAME_SIZE) * _zoom);
                }
            }
            painter.drawPixmap((_newNodePos->x() - minX - NODE_SIZE / 4) * _zoom,
                               (_newNodePos->y() - minY - NODE_SIZE / 4) * _zoom,
                               NODE_SIZE / 2 * _zoom, NODE_SIZE / 2 * _zoom,
                               _nodeIcons[_closestNode->getFather()->getType()]);
        }
    }
}

void DrawingArea::setZoom(double z)
{
    _zoom = z;
}

void DrawingArea::drawSubTree(QPainter& painter, Node* const node, int xRep, int yRep)
{
    if(node)
    {
        if(_newNodePos)
        {
            int dx = _newNodePos->x() - (xRep + minX + NODE_SIZE / 2);
            int dy = _newNodePos->y() - (yRep + minY + NODE_SIZE + NODE_NAME_SIZE);
            int dxOld = _newNodePos->x() - _closestNodePos.x();
            int dyOld = _newNodePos->y() - _closestNodePos.y();
            if(dx * dx + dy * dy < dxOld * dxOld + dyOld * dyOld)
            {
                _closestNode = node;
                _closestNodePos = QPoint(xRep + minX + NODE_SIZE / 2,
                                         yRep + minY + NODE_SIZE + NODE_NAME_SIZE);
            }
        }
        for(unsigned int i = 0 ; i < node->getChildren().size() ; i++)
        {
            painter.drawLine((xRep + NODE_SIZE / 2) * _zoom,
                             (yRep + NODE_SIZE + NODE_NAME_SIZE) * _zoom,
                             (xRep + node->getChildren()[i]->getX()
                                    + NODE_SIZE / 2) * _zoom,
                             (yRep + node->getChildren()[i]->getY()
                                    + NODE_SIZE / 2) * _zoom);
            //dessine les relations entre freres
            if(node->getChildren()[i]->getRelationType() != Node::NONE
            && node->getChildren()[i]->getRightSibling())
            {
                painter.drawLine((xRep + node->getChildren()[i]->getX()
                                        + NODE_SIZE / 2) * _zoom,
                                 (yRep + node->getChildren()[i]->getY()
                                        + NODE_SIZE / 2) * _zoom,
                                 (xRep + node->getChildren()[i]->getRightSibling()->getX()
                                        + NODE_SIZE / 2) * _zoom,
                                 (yRep + node->getChildren()[i]->getRightSibling()->getY()
                                        + NODE_SIZE / 2) * _zoom);
                painter.drawText((int)((xRep + node->getChildren()[i]->getX()
                                        + NODE_SIZE / 2) * _zoom
                                        + ((xRep + node->getChildren()[i]->getRightSibling()->getX()
                                        + NODE_SIZE / 2)
                                        -(xRep + node->getChildren()[i]->getX()
                                        + NODE_SIZE / 2))/2 * _zoom),
                                 (int)((yRep + node->getChildren()[i]->getY()
                                        + NODE_SIZE / 2) * _zoom
                                        + ((yRep + node->getChildren()[i]->getRightSibling()->getY()
                                        + NODE_SIZE / 2)
                                        -(yRep + node->getChildren()[i]->getY()
                                        + NODE_SIZE / 2))/2 * _zoom),
                                 _relationSymbols[node->getChildren()[i]->getRelationType()]);
            }
            drawSubTree(painter, node->getChildren()[i],
                        xRep + node->getChildren()[i]->getX(),
                        yRep + node->getChildren()[i]->getY());
        }
        painter.drawPixmap(xRep * _zoom, yRep * _zoom,
                           NODE_SIZE * _zoom, NODE_SIZE * _zoom,
                           _nodeIcons[node->getType()]);
        if (_model->getSelection() == node)
            painter.drawRect(xRep * _zoom, yRep * _zoom,
                               NODE_SIZE * _zoom, NODE_SIZE * _zoom);
        //affiche le nom du noeud
        std::ostringstream oss;
        oss << (node->isOptional() ? "[" : "")
            << node->getName()
            << (node->isIterative() ? "*" : "")
            << (node->isOptional() ? "]" : "");
        std::string str(oss.str());
        painter.drawText((int) ((xRep + NODE_SIZE/2 - (str.size()*FONT_SIZE)/3) * _zoom),
                         (int)((yRep + NODE_SIZE + NODE_NAME_SIZE/2) * _zoom),
                         str.c_str());
    }
}

void DrawingArea::mousePressEvent(QMouseEvent* e)
{
    switch(_state)
    {
        case IDLE :
            _pointerPosition = e->pos();
            if(e->button() == Qt::LeftButton)
                _model->selectNode(e->x() / _zoom + minX,
                                   e->y() / _zoom + minY);
            else if(e->button() == Qt::RightButton)
            {
                _newNodePos = new QPoint(e->x() / _zoom + minX,
                                         e->y() / _zoom + minY);
                repaint();
            }
            _state = CLICK_OR_DRAG;
            break;
        case CLICK_OR_DRAG :
            _state = CLICK_OR_DRAG;
            break;
        case DRAGGING :
            _state = DRAGGING;
            break;
    }
}

#define D_DRAG 4

void DrawingArea::mouseMoveEvent(QMouseEvent * e)
{
    int dx = e->x() - _pointerPosition.x();
    int dy = e->y() - _pointerPosition.y();
    switch(_state)
    {
        case IDLE :
            _state = IDLE;
            break;
        case CLICK_OR_DRAG :
            if(((dx * dx) + (dy * dy)) > (D_DRAG * D_DRAG))
            {
                if(_model->getSelection() && !_newNodePos)
                    _model->moveSelectedNodesGraphically((double) dx / _zoom,
                                                         (double) dy / _zoom);
                if(_newNodePos)
                {
                    *_newNodePos = QPoint(e->x() / _zoom + minX,
                                          e->y() / _zoom + minY);
                    repaint();
                }
                _pointerPosition = e->pos();
                _state = DRAGGING;
            }
            else
                _state = CLICK_OR_DRAG;
            break;
        case DRAGGING :
            if(_model->getSelection() && !_newNodePos)
                _model->moveSelectedNodesGraphically((double) dx / _zoom,
                                                     (double) dy / _zoom);
            if(_newNodePos)
            {
                *_newNodePos = QPoint(e->x() / _zoom + minX,
                                      e->y() / _zoom + minY);
                repaint();
            }
            _pointerPosition = e->pos();
            _state = DRAGGING;
            break;
    }
}

void DrawingArea::mouseReleaseEvent(QMouseEvent*)
{
    switch(_state)
    {
        case IDLE :
            _state = IDLE;
            break;
        case CLICK_OR_DRAG :
            _state = IDLE;
            if(_newNodePos)
            {
                addNewNode();
                delete _newNodePos;
                _newNodePos = NULL;
                repaint();
            }
            break;
        case DRAGGING :
            if(_newNodePos)
            {
                addNewNode();
                delete _newNodePos;
                _newNodePos = NULL;
                repaint();
            }
            _state = IDLE;
            break;
    }
}

void DrawingArea::addNewNode()
{
    int dx = _newNodePos->x() - _closestNodePos.x();
    int dy = _newNodePos->y() - _closestNodePos.y();

    if((dy > qAbs(dx)) || !_closestNode->getFather())
    {
        _closestNode->addChild(new Node());
        std::cout << dx << "   " << dy << std::endl;
    }
    else
    {
        if(dx >= 0)
        {
            if(_closestNode->getRightSibling())
                _closestNode->getRightSibling()->addLeftSibling(new Node());
            else
                _closestNode->getFather()->addChild(new Node());

        }
        else if(dx < 0)
            _closestNode->addLeftSibling(new Node());
    }
}
